<?php

declare (strict_types=1);

namespace app\api\service;

use EasyWeChat\Factory;
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
use EasyWeChat\Kernel\Exceptions\InvalidConfigException;
use EasyWeChat\Kernel\Exceptions\RuntimeException;
use EasyWeChat\OfficialAccount\Application;
use app\api\model\wxofficial\Setting as WxofficialSetting;
use app\common\service\BaseService;
use app\common\library\helper;
use app\common\library\wechat\WXBizDataCrypt;
use cores\exception\BaseException;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;

/**
 * 服务类：微信公众号
 * Class Wxofficial
 * @package app\api\service
 */
class Wxofficial extends BaseService
{
    /**
     * 微信SDK应用类
     * @var Application|null
     */
    private ?Application $app = null;

    /**
     * 获取授权登录地址
     * @param string $callbackUrl 回调的h5页面
     * @return string
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function getOauthUrl(string $callbackUrl): string
    {
        return $this->getApp()->oauth->redirect($callbackUrl)->getTargetUrl();
    }

    /**
     * 获取微信公众号基础设置
     * @return array
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    private function getWxofficialSetting(): array
    {
        $setting = WxofficialSetting::getItem('basic');
        return [
            'app_id' => $setting['appId'],
            'secret' => $setting['appSecret']
        ];
    }

    /**
     * 获取微信用户信息
     * @return array
     * @throws BaseException
     */
    public function getOauthUserInfo(): array
    {
        try {
            // 获取微信用户信息
            $userInfo = $this->getApp()->oauth->user();
            // 将敏感信息加密
            $encrypted = $this->encryptData([
                'openid' => $userInfo['original']['openid'],
                'unionid' => $userInfo['original']['unionid'] ?? '',
            ]);
            return [
                'userInfo' => [
                    'nickName' => $userInfo['original']['nickname'],
                    'avatarUrl' => $userInfo['original']['headimgurl'],
                    'gender' => $userInfo['original']['sex']
                ],
                'encryptedData' => $encrypted['encryptedData'],
                'iv' => $encrypted['iv'],
            ];
        } catch (\Throwable $e) {
            throwError('获取微信用户信息失败：' . $e->getMessage());
        }
    }

    /**
     * 获取微信公众号jssdk配置参数
     * @param string $url 用户端完整的url
     * @return array
     * @throws InvalidArgumentException
     * @throws InvalidConfigException
     * @throws RuntimeException
     * @throws \Psr\SimpleCache\InvalidArgumentException
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function getJssdkConfig(string $url): array
    {
        return $this->getApp()->jssdk->buildConfig([
            'checkJsApi',
            'updateAppMessageShareData',
            'updateTimelineShareData',
            'onMenuShareAppMessage',
            'onMenuShareTimeline',
            'onMenuShareQQ',
            'onMenuShareWeibo',
            'onMenuShareQZone',
            'hideOptionMenu',
        ], false, false, false, [], $url);
    }

    /**
     * 微信SDK应用类
     * @return Application
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    private function getApp(): ?Application
    {
        if (!$this->app) {
            $setting = $this->getWxofficialSetting();
            $this->app = Factory::officialAccount($setting);
        }
        return $this->app;
    }

    /**
     * 加密敏感信息
     * @param array $plainData
     * @return array
     */
    private function encryptData(array $plainData): array
    {
        $WXBizDataCrypt = new WXBizDataCrypt;
        $iv = $WXBizDataCrypt->createIv();
        $encryptedData = '';
        $plaintext = helper::jsonEncode($plainData);
        $WXBizDataCrypt->encryptData($plaintext, $iv, $encryptedData);
        return compact('iv', 'encryptedData');
    }
}